package com.w3c.pragmatic.controller;

import com.w3c.pragmatic.utils.RedissonUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 测试分布式锁
 *
 * @author wangdong
 * @since 2021/12/16 18:01
 */
@Slf4j
@RestController
@RequestMapping("/redisson")
@Api(tags = "分布式锁Redisson控制层RedissonController")
public class RedissonController {

  @Resource
  private RedissonClient redissonClient;

  // ===========================redissonClient==================================
  // ======================使用原生的redissonClient加锁===========================
  // ===========================redissonClient==================================

  @SneakyThrows
  @ApiOperation(value = "原生方式-获取锁(锁续命)", httpMethod = "GET")
  @RequestMapping(value = "/getLock/{operator}", method = RequestMethod.GET)
  public void redissonLock(@PathVariable String operator) {
    RLock lock = redissonClient.getLock("redisson_lock_key");
    //RLock lock = redissonClient.getFairLock("redisson_lock_key");  获取公平锁
    lock.lock();
    //lock.lock(15, TimeUnit.SECONDS);
    try {
      log.info("获取锁成功：{}", operator);
      log.info("当前用户：{}-{}，{}开始执行...", operator, Thread.currentThread().getId(), new Date());
      // 场景一：lock.lock()
      // 有看门狗，默认加锁30秒，并且每10秒主动检查当前线程是否执行完成，如果没有执行完成锁续命30秒
      // 如果任务10秒内完成，执行finally中的lock.unlock()，不会触发锁续命
      // 如果任务超过10秒完成，每10秒看门狗会重新续命30秒，直到任务完成（如果redis宕机/线程终止那么看门狗将不会续命，避免死锁），最终执行finally中的lock.unlock()

      // 场景二：lock.lock(15, TimeUnit.SECONDS);
      // 没有看门狗，拿到锁后15秒强制释放（自动释放不会调用lock.unlock()）
      // 如果A任务执行超过15秒，即使A任务还在执行，此时其他自旋的B线程也会得到锁，这时A执行完了调用lock.unlock();会报错因为A线程的锁被强制关闭了，这时候的锁是B线程占有
      TimeUnit.SECONDS.sleep(40);
      log.info("当前用户：{}-{}，{}执行完成", operator, Thread.currentThread().getId(), new Date());
    } finally {
      if (lock.isLocked() && lock.isHeldByCurrentThread()) {
        log.info("释放锁:{}", operator);
        lock.unlock();
      }
    }
  }

  @SneakyThrows
  @ApiOperation(value = "原生方式-获取锁(超时时间)", httpMethod = "GET")
  @RequestMapping(value = "/getTryLock/{operator}", method = RequestMethod.GET)
  public void redissonTryLock(@PathVariable String operator) {
    RLock lock = redissonClient.getLock("redisson_try_lock_key");
    //RLock lock = redissonClient.getFairLock("redisson_lock_key");  获取公平锁
    //lock.tryLock(50, 30, TimeUnit.SECONDS)
    if(lock.tryLock(10, TimeUnit.SECONDS)){
      try {
        log.info("获取锁成功：{}", operator);
        log.info("当前用户：{}-{}，{}开始执行...", operator, Thread.currentThread().getId(), new Date());
        // 场景一：lock.tryLock(10, TimeUnit.SECONDS)
        // 有看门狗，默认加锁30秒，并且每10秒主动检查当前线程是否执行完成，如果没有执行完成锁续命30秒
        // 如果任务10秒内完成，执行finally中的lock.unlock()，不会触发锁续命
        // 如果任务超过10秒完成，每10秒看门狗会重新续命30秒，直到任务完成（如果redis宕机/线程终止那么看门狗将不会续命，避免死锁），最终执行finally中的lock.unlock()
        // 对于自旋等待的线程最多等待10秒，10秒后拿不到锁，则返回失败（区别于lock的是不会一直自旋）

        // 场景二：lock.lock(50, 30, TimeUnit.SECONDS);
        // 没有看门狗，拿到锁后30秒强制释放（自动释放不会调用lock.unlock()）
        // 如果A任务执行超过30秒，即使A任务还在执行，此时其他自旋的B线程也会得到锁，这时A执行完了调用lock.unlock();会报错因为A线程的锁被强制关闭了，这时候的锁是B线程占有
        // 对于自旋等待的线程最多等待50秒，50秒后拿不到锁，则返回失败（区别于lock的是不会一直自旋）
        TimeUnit.SECONDS.sleep(20);
        log.info("当前用户：{}-{}，{}执行完成", operator, Thread.currentThread().getId(), new Date());
      } finally {
        if (lock.isLocked() && lock.isHeldByCurrentThread()) {
          log.info("释放锁:{}", operator);
          lock.unlock();
        }
      }
    }else{
      log.info("当前用户：{}， 获取锁失败", operator);
    }
  }

  // ===========================RedissonUtil==================================
  // ======================自定义RedissonUtil工具类加锁===========================
  // ===========================RedissonUtil==================================

  @SneakyThrows
  @ApiOperation(value = "工具方式-获取锁(锁续命)", httpMethod = "GET")
  @RequestMapping(value = "/getLock2/{operator}", method = RequestMethod.GET)
  public void redissonLock2(@PathVariable String operator) {
    String lockKey = "redisson_lock2_key";
    RedissonUtil.lock(lockKey);
    //RedissonUtil.lock(lockKey, 10);
    try {
      log.info("获取锁成功：{}", operator);
      log.info("当前用户：{}-{}，{}开始执行...", operator, Thread.currentThread().getId(), new Date());
      // do something
      TimeUnit.SECONDS.sleep(45);
      log.info("当前用户：{}-{}，{}执行完成", operator, Thread.currentThread().getId(), new Date());
    } finally {
      RedissonUtil.unlock(lockKey);
    }
  }

  @SneakyThrows
  @ApiOperation(value = "工具方式-获取锁(超时时间)", httpMethod = "GET")
  @RequestMapping(value = "/getTryLock2/{operator}", method = RequestMethod.GET)
  public void redissonTryLock2(@PathVariable String operator) {
    String lockKey = "redisson_try_lock2_key";
    // RedissonUtil.tryLock(lockKey, 10, 10)
    if(RedissonUtil.tryLock(lockKey, 10)){
      try {
        log.info("获取锁成功：{}", operator);
        log.info("当前用户：{}-{}，{}开始执行...", operator, Thread.currentThread().getId(), new Date());
        // do something
        TimeUnit.SECONDS.sleep(20);
        log.info("当前用户：{}-{}，{}执行完成", operator, Thread.currentThread().getId(), new Date());
      } finally {
        RedissonUtil.unlock(lockKey);
      }
    }else{
      log.info("当前用户：{}， 获取锁失败", operator);
    }
  }
}
